package com.twu.biblioteca;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class MenuController extends AbstractController {

    private BookRepository bookRepository;

    private MovieRepository movieRepository;

    private ReserveController reserveController;

    private LoginController loginController;

    private SecurityContext securityContext;

    MenuController(UserInterface userInterface, BookRepository bookRepository, MovieRepository movieRepository) {
        setUserInterface(userInterface);
        this.bookRepository = bookRepository;
        this.movieRepository = movieRepository;
    }

    void setReserveController(ReserveController reserveController) {
        this.reserveController = reserveController;
        this.reserveController.setNext(this);
    }

    void setLoginController(LoginController loginController) {
        this.loginController = loginController;
        this.loginController.setNext(this);
    }

    void setSecurityContext(SecurityContext securityContext) {
        this.securityContext = securityContext;
    }

    @Override
    public void enter() {
        getUserInterface().showMenu();
    }

    @Override
    public Controller handle(String input) {
        return select(input);
    }

    Controller select(String menuOption) {
        ActionCommand actionCommand = ActionCommand.findByCommandString(menuOption);
        final Controller result = actionCommand.execute(this, menuOption);
        return result;
    }

    Controller doList() {
        getUserInterface().showBookList(bookRepository.findAll());
        return this;
    }

    Controller doMembership() {
        if (securityContext.isUserLoggedIn() == true) {
            getUserInterface().showMembershipInfo(securityContext.getUser());
        } else {
            getUserInterface().showLoginRequiredMessage();
        }
        return this;
    }

    Controller doMovies() {
        getUserInterface().showMovieList(movieRepository.findAll());
        return this;
    }

    Controller doReserve() {
        final Controller result;
        if (securityContext.isUserLoggedIn() == true) {
            result = reserveController;
        } else {
            getUserInterface().showLoginRequiredMessage();
            result = this;
        }
        return result;
    }

    Controller doLogin() {
        return loginController;
    }

    Controller doUnknownCommand() {
        getUserInterface().showInvalidCommandMessage();
        return this;
    }

    Controller doQuit() {
        return NullController.getInstance();
    }

    static enum ActionCommand {
        LIST {
            @Override
            public Controller execute(MenuController menuController, String command) {
                return menuController.doList();
            }
        }, MOVIES {
            @Override
            public Controller execute(MenuController menuController, String command) {
                return menuController.doMovies();
            }
        }, MEMBERSHIP {
            @Override
            public Controller execute(MenuController menuController, String command) {
                return menuController.doMembership();
            }
        },
        // @James: From here on 'Pluggable Selector Design Pattern' (see #execute(MenuController, String) below)
        LOGIN, RESERVE, QUIT,
        __UNKNOWN__ {
            @Override
            public Controller execute(MenuController menuController, String command) {
                return menuController.doUnknownCommand();
            }
        };

        public static ActionCommand findByCommandString(String commandString) {
            for (ActionCommand value : values()) {
                if (commandString.equals(value.getCommandString())) {
                    return value;
                }
            }
            return __UNKNOWN__;
        }

        public String getCommandString() {
            return name().toLowerCase();
        }

        /*
         * @James: This is an implementation of the Pluggable Selector Design Pattern.
         * I don't like it too much because we loose the checkability of direct method invocations and have
         * a contract based on conventions.
         * But it safes some lines of code (compare LIST, MOVIES, MEMBERSHIP to LOGIN, RESERVE, QUIT).
         * Usually I would check back with a Dev lead to ask whether those techniques are desired or not.
         * With only 6 commands it might not make too much sense, but with more commands it could be useful
         * as it removes noise (and duplication).
         */
        public Controller execute(MenuController menuController, String command) {
            try {
                String methodName = "do" + command.substring(0, 1).toUpperCase() + command.substring(1);
                Method method = menuController.getClass().getDeclaredMethod(methodName);
                return Controller.class.cast(method.invoke(menuController));
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

    }

}
